#!/usr/bin/env python

"""
based on http://backbonedocs.drop.io
"""

# todo
# add documentation for response of methods such as delete, copy, move, wait for better documentation at drop.io
# add optional input subsricption id or just subscriptino

__author__ = 'jimmyorr@gmail.com (Jimmy Orr), chedbrandh@gmail.com (Christofer Hedbrandh)'
__version__ = '0.2.0'

import httplib
import logging
import mimetypes
import mimetools
import os.path
import sys
import urllib
import urllib2
import uuid
import time
import hashlib
from optparse import OptionParser
from urlparse import urlsplit

try: import json
except ImportError: import simplejson as json

from dropio.resource import *

_SIGNED_REQUEST_TIMEOUT = 60 * 10

_API_VERSION = '3.0'
_API_FORMAT = 'json'

_API_URL = 'http://api.drop.io/'
_UPLOAD_URL = 'http://assets.drop.io/upload'

_DROPS = 'drops/'
_ASSETS = '/assets/'
_SEND_TO = '/send_to'
_EMPTY = '/empty'
_COPY = '/copy'
_MOVE = '/move'
_DOWNLOAD_ORIGINAL = '/download/original'
_SUBSCRIPTIONS = '/subscriptions/'
_ACCOUNTS = 'accounts/'

#########################################################################
# HTTP ERRORS
#########################################################################

class Error(urllib2.HTTPError):
    def __init__(self, http_error):
        urllib2.HTTPError.__init__(self, http_error.filename, http_error.code,
            http_error.msg, http_error.hdrs, http_error.fp)

    def __str__(self):
        return "HTTP Error %s: %s: %s: %s" % (self.code, self.msg, self.filename, self.fp.readline())


class BadRequestError(Error):
    """400 Bad Request
    Something is wrong with the request in general (i.e. missing parameters,
    bad data, etc).
    """
    pass

class ForbiddenError(Error):
    """403 Forbidden
    You did not supply a valid API key.
    """
    pass


class ResourceNotFoundError(Error):
    """404 Not Found
    The resource requested is not found or not available.
    """
    pass


class InternalServerError(Error):
    """500 Internal Server Error
    Something that [drop.io] did not account for has gone wrong.
    """
    pass

class _NullHandler(logging.Handler):
    """default logger does nothing"""
    
    def emit(self, record):
        pass

#########################################################################
# DROPIO CLIENT
#########################################################################

class DropIoClient(object):
    """Client for the Drop.io service."""
    
    def __init__(self, api_key, api_secret = None,
        signed_request_timeout = _SIGNED_REQUEST_TIMEOUT, logger = None,
        api_url = _API_URL, upload_url = _UPLOAD_URL):
        self.__base_params_dict = {}
        self.__base_params_dict['api_key'] = api_key
        self.__base_params_dict['version'] = _API_VERSION
        self.__base_params_dict['format'] = _API_FORMAT
        if logger:
            self.logger = logger
        else:
            handler = _NullHandler()
            self.logger = logging.getLogger()
            self.logger.addHandler(handler)
        self.api_secret = api_secret if api_secret else None
        self.signed_request_timeout = signed_request_timeout
        self.api_url = api_url
        self.upload_url = upload_url
    
    def __add_signature_if_needed(self, params_dict):
        if self.api_secret is None:
            return False
        params_dict['timestamp'] = str(int(time.time()) + self.signed_request_timeout)
        # file uploads
        if 'file' in params_dict:
            if params_dict.get('signature_mode') == 'OPEN':
                signed_params_dict = {}
                signed_params_dict['api_key'] = params_dict['api_key']
                signed_params_dict['timestamp'] = params_dict['timestamp']
                signed_params_dict['version'] = params_dict['version']
                signed_params_dict['signature_mode'] = params_dict['signature_mode']
            elif params_dict.get('signature_mode') == 'NORMAL' or not params_dict.get('signature_mode'):
                signed_params_dict = {}
                signed_params_dict['api_key'] = params_dict['api_key']
                signed_params_dict['timestamp'] = params_dict['timestamp']
                signed_params_dict['version'] = params_dict['version']
                signed_params_dict['drop_name'] = params_dict['drop_name']
                signed_params_dict['format'] = params_dict['format']
                if 'signature_mode' in params_dict:
                    signed_params_dict['signature_mode'] = params_dict['signature_mode']
                if 'description' in params_dict:
                    signed_params_dict['description'] = params_dict['description']
                if 'convert_to' in params_dict:
                    signed_params_dict['convert_to'] = params_dict['convert_to']
                if 'pingback_url' in params_dict:
                    signed_params_dict['pingback_url'] = params_dict['pingback_url']
                if 'redirect_to' in params_dict:
                    signed_params_dict['redirect_to'] = params_dict['redirect_to']
                if 'output_locations' in params_dict:
                    signed_params_dict['output_locations'] = params_dict['output_locations']
            # STRICT
            else:
                signed_params_dict = dict(params_dict)
                del signed_params_dict['file']
        else:
            signed_params_dict = params_dict
        
        sha1_hash_object = hashlib.sha1()
        for key in sorted(signed_params_dict.keys()):
            sha1_hash_object.update(str(key) + '=' + str(signed_params_dict[key]))
        sha1_hash_object.update(self.api_secret)
        params_dict['signature'] = sha1_hash_object.hexdigest()
        
        return True
    
    def __http_error_handler(fn):
        def wrapper(*v, **k):
            try:
                result = fn(*v, **k)
            except urllib2.HTTPError, error:
                if error.code == 400:
                    raise BadRequestError(error)
                elif error.code == 403:
                    raise ForbiddenError(error)
                if error.code == 404:
                    raise ResourceNotFoundError(error)
                if error.code == 500:
                    raise InternalServerError(error)
                else:
                    raise error
            return result
        return wrapper
    
    @__http_error_handler
    def __download(self, file_name, base_url, params_dict):
        params = urllib.urlencode(params_dict)
        stream = urllib2.urlopen(base_url + '?' + params)
        local_file = open(file_name, 'w')
        local_file.write(stream.read())
        stream.close()
        local_file.close()
        return
    
    @__http_error_handler
    def __get(self, base_url, params_dict):
        params = urllib.urlencode(params_dict)
        stream = urllib2.urlopen(base_url + '?' + params)
        body_dict = json.load(stream)
        stream.close()
        return body_dict
    
    @__http_error_handler
    def __post(self, url, params_dict):
        params = urllib.urlencode(params_dict)
        stream = urllib2.urlopen(url, params)
        body_dict = json.load(stream)
        stream.close()
        return body_dict
    
    @__http_error_handler
    def __post_multipart(self, url, params_dict):
        
        def encode_multipart_formdata(params_dict):
            boundary = mimetools.choose_boundary()
            
            body = ''
            
            for key, value in params_dict.iteritems():
                if isinstance(value, tuple):
                    filename, value = value
                    body += '--%s\r\n' % boundary
                    body += 'Content-Disposition: form-data;'
                    body += 'name="%s";' % str(key)
                    body += 'filename="%s"\r\n' % str(filename)
                    body += 'Content-Type: %s\r\n\r\n' % str(get_content_type(filename))
                    body += '%s\r\n' % str(value)
                else:
                    body += '--%s\r\n' % boundary
                    body += 'Content-Disposition: form-data; name="%s"\r\n\r\n' % str(key)
                    body += '%s\r\n' % str(value)
            
            body += '--%s--\r\n' % boundary
            content_type = 'multipart/form-data; boundary=%s' % boundary
        
            return body, content_type
        
        def get_content_type(filename):
            return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
        
        body, content_type = encode_multipart_formdata(params_dict)
        headers = {'content-type': content_type}
        
        url_parts = urlsplit(url)
        connection = httplib.HTTPConnection(url_parts.netloc)
        connection.request('POST', url_parts.path, body, headers)
        response = connection.getresponse()
        body_dict = json.load(response)
        connection.close()
        return body_dict
    
    @__http_error_handler
    def __put(self, url, params_dict):
        opener = urllib2.build_opener(urllib2.HTTPHandler)
        request = urllib2.Request(url, data=json.dumps(params_dict))
        request.add_header('Content-Type', 'application/json')
        request.get_method = lambda: 'PUT'
        stream = opener.open(request)
        body_dict = json.load(stream)
        stream.close()
        opener.close()
        return body_dict
    
    @__http_error_handler
    def __delete(self, url, params_dict):
        opener = urllib2.build_opener(urllib2.HTTPHandler)
        request = urllib2.Request(url, data=json.dumps(params_dict))
        request.add_header('Content-Type', 'application/json')
        request.get_method = lambda: 'DELETE'
        stream = opener.open(request)
        
        body_dict = json.load(stream)
        stream.close()
        opener.close()
        return body_dict
    
    ################
    # DROP RESOURCE
    ################
    
    @staticmethod
    def __get_drop_params_to_create_drop_params(get_drop_params):
        create_drop_params = {}
        if 'name' in get_drop_params:
            create_drop_params['name'] = get_drop_params['name']
        if 'description' in get_drop_params:
            create_drop_params['description'] = get_drop_params['description']
        if 'max_size' in get_drop_params:
            create_drop_params['max_size'] = get_drop_params['max_size']
        if 'email_key' in get_drop_params:
            create_drop_params['email_key'] = get_drop_params['email_key']
        # max_bytes is in bytes, max_size is in mb
        if 'max_bytes' in get_drop_params:
            create_drop_params['max_size'] = str(int(get_drop_params['max_bytes'])/(2**20))
        # check for email key
        if 'email' in get_drop_params and get_drop_params['email'].count('.') == 2:
            create_drop_params['email_key'] = (get_drop_params['email'].split('@')[0]).split('.')[1]
        return create_drop_params
    
    def create_drop(self, drop = None, drop_name = None,
        description = None, email_key = None, max_size = None):
        """
        creates a drop
        if drop is specified, data attributes from drop will be used
        data attributes in drop have lower precedence than other specified arguments
        drop_name:
            this is the name of the drop and will become part of the URL of the drop
            valid characters are alphanumeric and the underscore (a-z, A-Z, 0-9, and _)
        description:
            a plain text description of a drop
        email_key:
            the security key tagged onto the email address created for a drop
        max_size:
            the max size of the drop, in megabytes
        Returns:
            dropio.response_bodies.Drop
        """

        params_dict = {}
        # add parameters from specified drop object
        if drop is not None:
            params_dict.update(self.__get_drop_params_to_create_drop_params(vars(drop)))
        # add any other specified parameters
        if drop_name is not None:
            params_dict['name'] = drop_name
        if description is not None:
            params_dict['description'] = description
        if email_key is not None:
            params_dict['email_key'] = email_key
        if max_size is not None:
            params_dict['max_size'] = max_size
        # add base parameters
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS
        drop_dict = self.__post(url, params_dict)
        result_drop = Drop(drop_dict)

        return result_drop
    
    def get_drop(self, drop = None, drop_name = None):
        """
        gets a drop
        Returns:
            dropio.response_bodies.Drop
        """
        assert drop_name or (drop and drop.name)
        
        drop_name = drop_name or drop.name
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name
        drop_dict = self.__get(url, params_dict)
        result_drop = Drop(drop_dict)
        
        return result_drop
    
    def __get_drops_by_page(self, page = 1):
        params_dict = {}
        params_dict['page'] = page
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _ACCOUNTS + _DROPS
        response = self.__get(url, params_dict)
        for drop_dict in response['drops']:
            yield Drop(drop_dict)
        
        return
    
    def get_drops(self):
        """
        Note:
            modifying iterator while iterating is unsafe
        Returns:
            generator of dropio.response_bodies.Drop instances
        """
        
        page = 1
        while True:
            drops = self.__get_drops_by_page(page = page)
            empty = True
            for drop in drops:
                yield drop
                empty = False
            if empty:
                break
            page += 1
        
        return

    def update_drop(self, drop = None, drop_name = None, new_name = None,
        description = None, email_key = None, max_size = None):
        """
        updates a drop
        if drop is specified, data attributes from drop will be used
        data attributes in drop have lower precedence than other specified arguments
        if drop_name and drop.name is specified, then drop.name will be the new name of the drop
        new_name:
            this is the name of the drop and will become part of the URL of the drop
            valid characters are alphanumeric and the underscore (a-z, A-Z, 0-9, and _)
        description:
            a plain text description of a drop
        email_key:
            the security key tagged onto the email address created for a drop
        max_size:
            the max size of the drop, in megabytes
        Returns:
            dropio.response_bodies.Drop
        """
        assert drop_name or (drop and drop.name)
        
        drop_name = drop_name or drop.name

        params_dict = {}
        # add parameters from specified drop object
        if drop is not None:
            params_dict.update(self.__get_drop_params_to_create_drop_params(vars(drop)))
        # add any other specified parameters
        if new_name is not None:
            params_dict['name'] = new_name
        if description is not None:
            params_dict['description'] = description
        if email_key is not None:
            params_dict['email_key'] = email_key
        if max_size is not None:
            params_dict['max_size'] = max_size
        # add base parameters
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name
        drop_dict = self.__put(url, params_dict)
        drop = Drop(drop_dict)
        
        return drop
    
    def delete_drop(self, drop = None, drop_name = None):
        """
        deletes a drop
        """
        assert drop_name or (drop and drop.name)
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + (drop_name or drop.name)
        return self.__delete(url, params_dict)
    
    def empty_drop(self, drop = None, drop_name = None):
        """
        empties a drop
        """
        assert drop_name or (drop and drop.name)
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + (drop_name or drop.name) + _EMPTY
        return self.__put(url, params_dict)
    
    
    #################
    # ASSET RESOURCE
    #################
    
    def create_link(self, url, drop = None, drop_name = None,
        title = None, description = None):
        """
        creates a link
        Returns:
            dropio.response_bodies.Asset
        """
        assert drop_name or (drop and drop.name)
        assert url
        
        drop_name = drop_name or drop.name
        
        params_dict = {}
        params_dict['url'] = url
        if title:
            params_dict['title'] = title
        if description:
            params_dict['description'] = description
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS
        asset_dict = self.__post(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def create_note(self, contents, drop = None, drop_name = None,
        title = None, description = None):
        """
        creates a note
        Returns:
            dropio.response_bodies.Asset
        """
        assert drop_name or (drop and drop.name)
        assert contents
        
        drop_name = drop_name or drop.name

        params_dict = {}
        params_dict['contents'] = contents
        if title:
            params_dict['title'] = title
        if description:
            params_dict['description'] = description
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS
        asset_dict = self.__post(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def create_file_from_readable(self, drop_name, readable, file_name = None, **other_params):
        """
        creates a file from readable e.g. sys.stdin or open(file_name)
        Returns:
            dropio.response_bodies.Asset
        """
        assert drop_name
        assert hasattr(readable, 'read')
        
        file_name = file_name or str(uuid.uuid4())
        
        params_dict = dict([(k,v) for (k,v) in other_params.items() if v is not None])
        params_dict['drop_name'] = drop_name
        params_dict['file'] = (file_name, readable.read())
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.upload_url
        asset_dict = self.__post_multipart(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def create_file(self, file_name, drop = None, drop_name = None,
        description = None, output_locations = None, pingback_url = None,
        signature_mode = 'NORMAL'):
        """
        creates a file from a file_name
        output_locations:
            Optional comma separated string or list of strings, of available output location strings
            Currently available built-in locations: DropioS3 (more coming soon as of 20100811)
            After you have registered an external storage location, you will be provided with a output
            location token. Passing that token to this argument will result in the original file
            uploaded as well as any converted files being stored in your storage location.
            You can pass multiple output_location strings - the asset and it's conversions will
            be copied to all specified locations.
            example :output_locations=DropioS3,sl_ce2f29d01a82f12da16eae5f8802078d 
            Would copy the asset and it's conversions to the default DropioS3
            location as well as a user-create S3 bucket. 
        pingback_url:
            A fully qualified URL which we will send a request to once the conversion process is
            complete. This gives you push notification of conversion jobs.
        signature_mode:
            signature_mode can be NORMAL, OPEN or STRICT (see drop.io api documentation)
        Returns:
            dropio.response_bodies.Asset
        """
        assert drop_name or (drop and drop.name)
        assert file_name
        assert os.path.isfile(file_name)
        assert signature_mode == 'NORMAL' or signature_mode == 'OPEN' or signature_mode == 'STRICT'
        
        drop_name = drop_name or drop.name
        if output_locations is not None and type(output_locations) == 'list':
            output_locations = ",".join(output_locations)
        stream = open(file_name, 'rb')
        asset = self.create_file_from_readable(drop_name, stream, file_name,
            description = description, output_locations = output_locations,
            pingback_url = pingback_url, signature_mode = signature_mode)
        stream.close()
        
        return asset
    
    def create_file_from_url(self, file_url, drop = None, drop_name = None, description = None,
        pingback_url = None):
        """
        creates a file from a file_url
        pingback_url:
            A fully qualified URL which we will send a request to once the conversion process is
            complete. This gives you push notification of conversion jobs.
        Returns:
            dropio.response_bodies.Asset
        """
        assert drop_name or (drop and drop.name)
        assert file_url

        drop_name = drop_name or drop.name

        params_dict = {}
        params_dict['file_url'] = file_url
        if description:
            params_dict['description'] = description
        if pingback_url:
            params_dict['pingback_url'] = pingback_url
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS
        asset_dict = self.__post(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def __get_assets_by_page(self, drop_name, page = 1, order = 'oldest'):
        assert drop_name
        
        params_dict = {}
        params_dict['page'] = page
        params_dict['order'] = order
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS
        response = self.__get(url, params_dict)
        
        for asset_dict in response['assets']:
            yield Asset(asset_dict)
        
        return
    
    def get_assets(self, drop = None, drop_name = None, oldest_first = True):
        """
        if oldest_first is True, then oldest asset will be returned first
        Note:
            modifying iterator while iterating is unsafe
        Returns:
            generator of dropio.response_bodies.Asset instances
        """
        assert drop_name or (drop and drop.name)
        
        drop_name = drop_name or drop.name
        order = 'oldest' if oldest_first else 'latest'
        
        page = 1
        while True:
            assets = self.__get_assets_by_page(drop_name = drop_name, page = page, order = order)
            empty = True
            for asset in assets:
                yield asset
                empty = False
            if empty:
                break
            page += 1
        
        return
    
    def get_asset(self, asset = None, asset_name = None, drop = None, drop_name = None):
        """
        Returns:
            dropio.resource.Asset
        """
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name
        asset_dict = self.__get(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def update_asset(self, drop = None, drop_name = None, asset = None, asset_name = None,
        title = None, description = None, url = None, contents = None):
        """
        updates an asset
        if asset is specified, data attributes from asset will be used
        data attributes in asset have lower precedence than other specified arguments
        url:
            only used when updating a link
        contents:
            only used when updating a note
        Returns:
            dropio.resource.Asset
        """
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        params_dict = {}
        # parameters from asset
        if asset and 'title' in vars(asset):
            params_dict['title'] = asset.title
        if asset and 'description' in vars(asset):
            params_dict['description'] = asset.description
        if asset and 'url' in vars(asset):
            params_dict['url'] = asset.url
        if asset and 'contents' in vars(asset):
            params_dict['contents'] = asset.contents
        # other parameters
        if title:
            params_dict['title'] = title
        if description:
            params_dict['description'] = description
        if url:
            params_dict['url'] = url
        if contents:
            params_dict['contents'] = contents
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name
        asset_dict = self.__put(url, params_dict)
        asset = Asset(asset_dict)
        
        return asset
    
    def delete_asset(self, drop = None, drop_name = None, asset = None, asset_name = None):
        """
        deletes an asset
        """
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name
        return self.__delete(url, params_dict)
    
    def copy_asset(self, target_drop = None, target_drop_name = None, drop = None, drop_name = None,
        asset = None, asset_name = None):
        """
        copies an asset to a target_drop
        """
        assert target_drop_name or (target_drop and target_drop.name)
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        target_drop_name = target_drop_name or target_drop.name
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        params_dict = {}
        params_dict['drop_name'] = target_drop_name
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name + _COPY
        return self.__post(url, params_dict)
    
    def move_asset(self, target_drop = None, target_drop_name = None, drop = None, drop_name = None,
        asset = None, asset_name = None):
        """
        moves an asset to a target_drop
        """
        assert target_drop_name or (target_drop and target_drop.name)
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        target_drop_name = target_drop_name or target_drop.name
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        params_dict = {}
        params_dict['drop_name'] = target_drop_name
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name + _MOVE
        return self.__post(url, params_dict)
    
    def download_asset_original_file(self, file_name = None, drop = None, drop_name = None,
        asset = None, asset_name = None):
        """
        downloads original file of file asset to file_name
        """
        assert drop_name or (drop and drop.name)
        assert asset_name or (asset and asset.name)
        
        drop_name = drop_name or drop.name
        asset_name = asset_name or asset.name
        
        if file_name is None:
            asset = self.get_asset(drop_name = drop_name, asset_name = asset_name)
            file_name = asset.roles['original_content']['filename']
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _ASSETS + asset_name + _DOWNLOAD_ORIGINAL
        return self.__download(file_name, url, params_dict)
    

    ###################
    # SUBSCRIPTION RESOURCE
    ###################
    
    def create_subscription(self, url = None, subscription = None, drop = None, drop_name = None,
            asset_added = True, asset_updated = False, asset_deleted = False):
        """
        creates a pingback subscription
        url:
            this is the url that pingback posts will be sent to (i.e. your server)
            other arguments specify possible events you can subscribe to
        Returns:
            dropio.response_bodies.Subscription
        """
        # todo will be changed to .url soon
        assert url or (subscription and subscription.username)
        # assert url or (subscription and subscription.url)
        assert drop_name or (drop and drop.name)
        assert asset_added or asset_updated or asset_deleted
        
        url = url or subscription.username
        # todo will be changed to .url soon
        # url = url or subscription.url
        drop_name = drop_name or drop.name
        
        params_dict = {}
        params_dict['type'] = 'pingback'
        params_dict['url'] = url
        if asset_added:
            params_dict['asset_added'] = 'true'
        if asset_updated:
            params_dict['asset_updated'] = 'true'
        if asset_deleted:
            params_dict['asset_deleted'] = 'true'
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _SUBSCRIPTIONS
        subscription_dict = self.__post(url, params_dict)
        subscription = Subscription(subscription_dict)
        
        return subscription
    
    def get_subscription(self, subscription = None, subscription_id = None, drop = None, drop_name = None):
        """
        Returns:
            dropio.response_bodies.Subscription
        """
        assert subscription_id or (subscription and subscription.id)
        assert drop_name or (drop and drop.name)
        
        subscription_id = subscription_id or subscription.id
        drop_name = drop_name or drop.name
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)

        url = self.api_url + _DROPS + drop_name + _SUBSCRIPTIONS + str(subscription_id)
        subscription_dict = self.__get(url, params_dict)
        subscription = Subscription(subscription_dict)
        
        return subscription
    
    def __get_subscriptions_by_page(self, drop_name, page = 1):
        assert drop_name
        
        params_dict = {}
        params_dict['page'] = page
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _SUBSCRIPTIONS
        response = self.__get(url, params_dict)
        for subscription_dict in response['subscriptions']:
            yield Subscription(subscription_dict)
        
        return
    
    def get_subscriptions(self, drop = None, drop_name = None):
        """
        Note:
            modifying iterator while iterating is unsafe
        Returns:
            generator of dropio.response_bodies.Subscription instances
        """
        assert drop_name or (drop and drop.name)
        
        drop_name = drop_name or drop.name
        
        page = 1
        while True:
            subscriptions = self.__get_subscriptions_by_page(drop_name = drop_name, page = page)
            empty = True
            for subscription in subscriptions:
                yield subscription
                empty = False
            if empty:
                break
            page += 1
        
        return
    
    def delete_subscription(self, subscription = None, subscription_id = None, drop = None, drop_name = None):
        """
        deletes a subscription
        """
        assert subscription_id or (subscription and subscription.id)
        assert drop_name or (drop and drop.name)
        
        subscription_id = subscription_id or subscription.id
        drop_name = drop_name or drop.name
        
        params_dict = {}
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        url = self.api_url + _DROPS + drop_name + _SUBSCRIPTIONS + str(subscription_id)

        return self.__delete(url, params_dict)
    
    ###################
    # UTILITY FUNCTIONS
    ###################

    def get_simple_upload_form(self, drop = None, drop_name = None,
        description = None, redirect_to = None,
        output_locations = None, pingback_url = None, signature_mode = 'NORMAL'):
        """
        Returns:
            HTML code for an upload form
        """
        assert drop_name or (drop and drop.name)
        assert signature_mode == 'NORMAL' or signature_mode == 'OPEN' or signature_mode == 'STRICT'
        
        params_dict = {}
        params_dict['drop_name'] = drop_name or drop.name
        params_dict['signature_mode'] = signature_mode
        if description:
            params_dict['description'] = description
        if redirect_to:
            params_dict['redirect_to'] = redirect_to
        if output_locations:
            params_dict['output_locations'] = output_locations
        if pingback_url:
            params_dict['pingback_url'] = pingback_url
        params_dict.update(self.__base_params_dict)
        self.__add_signature_if_needed(params_dict)
        
        input_tags = []
        for (k, v) in params_dict.items():
            input_tags += ['<input type="hidden" name="%s" value="%s">\n' % (k, v)]
        
        html_code = \
            '<form action="%s" method="post" enctype="multipart/form-data">\n' % self.upload_url + \
            '  <p>\n' + \
            '    <label for="file">Add a new file:</label>\n' + \
            '  </p>\n' + \
            '  <p>\n' + \
            '    <input type="file" name="file" size="25"/>\n' + \
            '  </p>\n' + \
            '  <p>\n'
        for input_tag in input_tags:
            html_code += \
            '    %s' % input_tag
        html_code += \
            '    <input type="submit" value="submit"/>\n' + \
            '  </p>\n' + \
            '</form>\n'
        return html_code

# todo switch to argparse
def main(argv=None):
    usage = "usage: %prog [options]"
    parser = OptionParser(usage, version="%prog " + __version__)
    
    parser.set_defaults(api_key=None,
                        verbosity=0,
                        drop_name=None,
                        token=None,
                        files_to_create=[],
                        links_to_create=[],
                        notes_to_create=[])
    
    parser.add_option("-k", "--key", 
                      action="store", dest="api_key", metavar="API_KEY",
                      help="REQUIRED! get key from http://api.drop.io/")
    parser.add_option("-v", "--verbose", 
                      action="count", dest="verbosity")
    parser.add_option("-d", "--drop_name", 
                      action="store", dest="drop_name", metavar="DROP")
    parser.add_option("-t", "--token", 
                      action="store", dest="token", metavar="TOKEN")
    parser.add_option("-f", "--file", 
                      action="append", dest="files_to_create", metavar="FILE",
                      help="Use a single dash '-' to read from stdin")
    parser.add_option("-l", "--link", 
                      action="append", dest="links_to_create", metavar="LINK")
    parser.add_option("-n", "--note",
                      action="append", dest="notes_to_create", metavar="NOTE")
    (options, unused_args) = parser.parse_args()
    
    if options.api_key is None:
        print parser.expand_prog_name("%prog: --key is a required option")
        print parser.expand_prog_name("Try `%prog --help' for more information.")
        return 1
    
    logger = logging.getLogger()
    logging_level = logging.WARNING - (options.verbosity * 10)
    logger.setLevel(logging_level)
    handler = logging.StreamHandler()
    handler.setLevel(logging_level)
    logger.addHandler(handler)
    
    client = DropIoClient(options.api_key, logger)
    
    try:
        drop = client.get_drop(drop_name = options.drop_name, token = options.token)
    except Exception:  # TODO: fix diaper anti-pattern
        drop = client.create_drop(drop_name = options.drop_name)
    
    for file_to_create in options.files_to_create:
        logger.info("Adding file %s to drop %s" % (file_to_create, drop.name))
        if file_to_create == "-":
            client.create_file_from_readable(drop.name, sys.stdin, token = options.token)
        else:
            client.create_file(file_to_create, drop = drop, token = options.token)
    
    for link_to_create in options.links_to_create:
        logger.info("Adding link '%s' to drop %s" % (link_to_create, drop.name))
        client.create_link(link_to_create, drop = drop, token = options.token)
    
    for note_to_create in options.notes_to_create:
        logger.info("Adding %s to drop %s" % (note_to_create, drop.name))
        client.create_note(note_to_create, drop = drop, token = options.token)
    
    return 0


if __name__ == "__main__":
    sys.exit(main())